﻿using System;
using System.Threading;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using xEngine.Debugging;
using xEngine.UI.Screens;
using xEngine.Rendering;
using xEngine.DataTypes;

namespace xEngine.UI
{
    public class ScreenManager : DrawableGameComponent
    {
        #region Member Variables

        private Point __designResolution;

        private AutoResetEvent _resetEvent;
        private List<Screen> _screens;
        private List<Screen> _needsLoading;
        private int _active;
        private LoadingScreen _loadingScreen;
        private Thread _loadingThread;
        private bool _closing;

        #endregion

        #region Properties

        public SpriteBatch SpriteBatch { get; private set; }
        public LoadingScreen LoadingScreen
        {
            get { return _loadingScreen; }
            set
            {
                _loadingScreen = value;
                _loadingScreen.Initialize(this);
                _loadingScreen.LoadContent();
            }
        }
        public int ActiveScreen
        {
            get { return _active; }
            set
            {
                if (value < 0 || value >= _screens.Count)
                    return;

                _active = value;
                _screens[_active].FadeIn();

                if (OnScreenChanged != null)
                    OnScreenChanged(this, new ScreenChangedEventArgs(_active, this[_active]));
            }
        }
        public Point DesignResolution
        {
            get { return __designResolution; }
            set
            {
                __designResolution = value;
                DesignScale = new PointF((float)GraphicsDevice.Viewport.Width / (float)__designResolution.X, (float)GraphicsDevice.Viewport.Height / (float)__designResolution.Y);
            }
        }
        public PointF DesignScale { get; private set; }

        public List<Screen> Screens { get { return _screens; } }

        public Screen this[int index]
        {
            get
            {
                if (index < 0 || index >= _screens.Count)
                    return null;
                else
                    return _screens[index];
            }
        }

        #endregion

        #region Constructors

        public ScreenManager(Game game) : base(game)
        {
            _closing = false;
            _active = -1;
            _screens = new List<Screen>();
            _needsLoading = new List<Screen>();
            SpriteBatch = new SpriteBatch(game.GraphicsDevice);
            _resetEvent = new AutoResetEvent(false);
            _loadingThread = new Thread(LoadContents);
            _loadingThread.Name = "xEngine_ScreenManager_LoadingThread";
            _loadingThread.Start();
        }

        #endregion

        #region Events

        public event EventHandler OnVideoSettingsChanged;
        public event EventHandler<ScreenChangedEventArgs> OnScreenChanged;
        public event EventHandler OnFinishedLoading;

        #endregion

        #region Functions

        public void VideoSettingsChanged()
        {
            DesignScale = new PointF((float)GraphicsDevice.Viewport.Width / (float)__designResolution.X, (float)GraphicsDevice.Viewport.Height / (float)__designResolution.Y);

            if (OnVideoSettingsChanged != null)
                OnVideoSettingsChanged(this, EventArgs.Empty);
        }

        public void AddScreen(Screen screen)
        {
            if (ActiveScreen <= 0)
            {
                _screens.Add(screen);
            }
            
            _needsLoading.Add(screen);

            screen.Initialize(this);

            if (_loadingScreen != null)
            {
                _loadingScreen.Show();
                _loadingScreen.Max++;
            }

            if (_needsLoading.Count == 1)
                _resetEvent.Set();
        }
        public void RemoveScreen(Screen screen)
        {
            _screens.Remove(screen);
            screen.UnloadContent();
        }

        public void SkipTo(int i)
        {
            if (i > -1 && i < _screens.Count)
            {
                for (int s = 0; s < i; s++)
                    if (_screens[s].Active)
                        _screens[s].FadeOut();

                ActiveScreen = i;
            }
        }

        public void LoadContents()
        {
            bool stateChanged = false;

            while (!_closing)
            {
                while (_needsLoading.Count > 0)
                {
                    stateChanged = true;

                    _needsLoading[0].LoadContent();
                    if (_needsLoading.Count > 0) 
                        XConsole.WriteLine("Finished Loading screen: " + _needsLoading[0].GetType().Name);
                    if (_loadingScreen != null)
                        _loadingScreen.Current++;
                    if (_needsLoading.Count > 0)
                        _needsLoading.RemoveAt(0);
                }

                if (stateChanged)
                {
                    if (OnFinishedLoading != null)
                        OnFinishedLoading(this, EventArgs.Empty);

                    if (ActiveScreen == -1)
                        ActiveScreen = 0;

                    stateChanged = false;
                }

                _resetEvent.WaitOne();
            }
        }

        public override void Initialize()
        {
            base.Initialize();

            DesignResolution = new Point(GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height);
        }

        protected override void LoadContent()
        {
        }
        protected override void UnloadContent()
        {
            _closing = true;
            _resetEvent.Set();

            _loadingScreen.UnloadContent();

            foreach (Screen screen in _screens)
            {
                screen.UnloadContent();
            }

            _screens.Clear();
            _needsLoading.Clear();
        }

        public override void Update(GameTime gameTime)
        {
            if (_needsLoading.Count == 0)
            {
                foreach (Screen screen in _screens)
                {
                    if (screen.Active || screen.UpdateWhileInactive)
                        screen.Update(gameTime);
                    if (screen.State != ScreenState.Normal)
                        screen.ProcessTransition(gameTime);
                }
            }
            else
            {
                if (_loadingScreen != null)
                    _loadingScreen.Update(gameTime);
            }
        }
        public override void Draw(GameTime gameTime)
        {
            if (_needsLoading.Count == 0)
            {
                if (ActiveScreen > -1)
                    _screens[ActiveScreen].Render(gameTime);
            }
            else
            {
                if (_loadingScreen != null)
                    _loadingScreen.Render(gameTime);
            }
        }

        #endregion
    }

    public class ScreenChangedEventArgs : EventArgs
    {
        public Screen CurrentScreen { get; private set; }
        public int CurrentScreenID { get; private set; }

        public ScreenChangedEventArgs(int id, Screen screen)
        {
            CurrentScreen = screen;
            CurrentScreenID = id;
        }
    }
}
